/*
 * uart_proxy.c - 串口代理
 *
 * Author: xiaobo (xiaobocwb@gmail.com)
 * Version: 1.0.0
 * Date: 2024-01
 *
 * Copyright (c) 2024 xiaobo
 * Licensed under the MIT License
 */

#include "uart_proxy.h"
#include "port_aos.h"
#include "xp_lpuart.h"
#include "aos/kernel.h"
#include "ota/ota_service.h"
#include "../middleware/uagent/ota/hal/ota_hal_ctrl.h"

#define CHECK_ERR(a, str, goto_tag, ...)                                                \
    do                                                                                  \
    {                                                                                   \
        if (!(a))                                                                       \
        {                                                                               \
            LOGE("%s(%d): " str, ##__VA_ARGS__);         		                        \
            goto goto_tag;                                                              \
        }                                                                               \
    } while (0)

#define CHECK_NULL(a, str, goto_tag, ...)   CHECK_ERR(a, str, goto_tag, ##__VA_ARGS__)
#define CHECK_NZERO(a, str, goto_tag, ...)  CHECK_ERR(0U == (a), str, goto_tag, ##__VA_ARGS__)

#define MQTT_PUBLISH_TOPIC  0x01
#define MQTT_RECEIVE_TOPIC  0x02

#define APP_VERSION_TOPIC   0x03

#define OTA_INFO_TOPIC      0x04
#define OTA_DATA_TOPIC      0x05
#define OTA_END_TOPIC       0x06
#define OTA_STATE_TOPIC     0x07

#define OVERTIME_MS (30 * 1000)

typedef enum {
    OTA_STATE_INIT = 0,
    OTA_STATE_ALLOW_DOWNLOAD,
    OTA_STATE_DOWNLOADING,
    OTA_STATE_DOWNLOAD_COMPLETE,
    OTA_STATE_ERROR,
} ota_state_t;

typedef struct
{
    uint32_t firmware_size;
    uint8_t md5_hash[16];
    char version[32];
}img_info_t;

static aos_sem_t recv_sem;
static bool is_recv_thread_run = false;
static protocol_t proto = NULL;
static uart_proxy_cb_ops_t* uart_proxy_cb_ops = NULL;
static uint8_t uart_proxy_port;

static uint64_t ota_timestamp;
static bool is_thread_run = false;
static ota_state_t ota_state = OTA_STATE_ERROR;
static uint32_t offset = 0;
static img_info_t img_info;


/*******************************************************************************/
static void ota_upgrade_task(ota_service_t* ctx)
{
    ota_state = OTA_STATE_INIT;

    // 应用层确保可允许升级
    {
        aos_msleep(5 * 1000);
        // extern bool is_device_allow_OTA(void);
        // extern void OTA_start_stop_service(void);

        // uint16_t timeOutCnt = 0;
        // while (timeOutCnt++ < 200 && !is_device_allow_OTA())
        // {
        //     LOGD("Wait device wash service finish, wait time %d S", 3*timeOutCnt);
        //     aos_msleep(3000);
        // }
        // CHECK_ERR(is_device_allow_OTA(), "App not allow upgrade", err_1);
        // OTA_start_stop_service();
    }

    const char* curr_version = (char*)ota_hal_version(0, NULL);
    CHECK_NULL(curr_version, "ota_hal_version failed", err_1);

    CHECK_ERR(strncmp(img_info.version, curr_version, strlen(curr_version)) > 0, "update version lower than current", err_1);

    ota_boot_param_t ota_param;
    CHECK_NZERO(ota_read_parameter(&ota_param), "ota_read_parameter failed", err_1);

    ota_hex2str(&ota_param.hash[0], img_info.md5_hash, sizeof(ota_param.hash), 16);
    ota_param.len = img_info.firmware_size;
    ota_param.upg_flag = 0x9669;
    ota_param.upg_magic = 0xEFEFEFEF;
    ota_param.hash_type = 2;
    ota_param.upg_status = OTA_TRANSPORT;

    CHECK_NZERO(ota_update_parameter(&ota_param), "ota_update_parameter failed", err_1);

    LOGI("ota_param.len: %d %.2fKB", ota_param.len, ota_param.len / 1024.0f);
    LOGI("ota_param.upg_flag: 0x%04X", ota_param.upg_flag);
    LOGI("ota_param.upg_magic: 0x%08X", ota_param.upg_magic);
    LOGI("ota_param.hash_type: %d", ota_param.hash_type);
    LOGI("ota_param.upg_status: %d", ota_param.upg_status);
    LOGI("ota_param.hash: %02X%02X...%02X%02X", ota_param.hash[0], ota_param.hash[1], ota_param.hash[30], ota_param.hash[31]);

    CHECK_NZERO(ota_int(&ota_param), "ota_int failed", err_1);

    ota_state = OTA_STATE_ALLOW_DOWNLOAD;

    ota_timestamp = aos_now_ms();
    // 等待下载固件数据完成
    while (aos_now_ms() - ota_timestamp < OVERTIME_MS
        && (OTA_STATE_ALLOW_DOWNLOAD == ota_state || OTA_STATE_DOWNLOADING == ota_state))
    {
        aos_msleep(50);
    }

    CHECK_ERR(OTA_STATE_DOWNLOAD_COMPLETE == ota_state, "ota state error", err_1);
    CHECK_NZERO(ota_verify(&ota_param), "ota_verify failed", err_1);
    CHECK_NZERO(ota_hal_boot(&ota_param), "ota_hal_boot failed", err_1);
    CHECK_NZERO(ota_parameter_save(NULL), "", err_1);
    LOGI("update end");
    aos_msleep(100);
    if (uart_proxy_cb_ops && uart_proxy_cb_ops->ota_update_complete_callback) {
        uart_proxy_cb_ops->ota_update_complete_callback();
    }
    else {
        aos_reboot();
    }
    is_thread_run = false;
    LOGI("ota_upgrade_task exit");
    return;
err_1:
    ota_state = OTA_STATE_ERROR;
    is_thread_run = false;
    LOGE("ota_upgrade_task exit");
    return;
}

bool start_upgrade_task(void)
{
    CHECK_ERR(!is_thread_run, "ota_upgrade_task already run", err_1);
    CHECK_NZERO(aos_task_new("ota_upgrade_task", ota_upgrade_task, NULL, 8192), "ota_upgrade_task creat failed", err_1);
    is_thread_run = true;

    return true;
err_1:
    return false;
}

static void mqtt_receive_callback(uint8_t msgid, uint8_t* payload, size_t len) {
    LOGD("Recv mqtt info, len=%ld, payload=%d", len, *(uint32_t*)payload);
    if (uart_proxy_cb_ops && uart_proxy_cb_ops->mqtt_receive_callback) {
        uart_proxy_cb_ops->mqtt_receive_callback(payload, len);
    }
}

static void app_version_callback(uint8_t msgid, uint8_t* payload, size_t len) {
    LOGD("Recv get version request, len=%ld, payload=%d", len, *(uint32_t*)payload);
    const char* version = ota_hal_version(0, NULL);
    protocol_publish(proto, APP_VERSION_TOPIC, (uint8_t*)version, strlen(version), true, 100);
}

static void app_state_callback(uint8_t msgid, uint8_t* payload, size_t len) {
    LOGD("Recv get state request, len=%ld, payload=%d", len, *(uint32_t*)payload);
    protocol_publish(proto, OTA_STATE_TOPIC, (uint8_t*)&ota_state, sizeof(ota_state), false, 0);
}

// 接收ota开始信息，尝试启动ota升级, 并通知ota状态（可能不允许升级）
static void ota_info_callback(uint8_t msgid, uint8_t* payload, size_t len) {
    LOGD("Recv ota start info, len=%ld", len);
    CHECK_ERR(NULL != payload && len > 20 && len < sizeof(img_info), "ota info error", err_1); // 20 = sizeof(firmware_size) + sizeof(md5_hash)

    memset(&img_info, 0, sizeof(img_info));
    memcpy(&img_info, payload, len);

    CHECK_ERR(start_upgrade_task(), "start_upgrade_task failed", err_1);
    return;
err_1:
    return;
}

// 接收ota固件数据
static void ota_data_callback(uint8_t msgid, uint8_t* payload, size_t len) {
    // LOGD("Recv ota data, len=%ld", len);

    CHECK_ERR((ota_state == OTA_STATE_ALLOW_DOWNLOAD || ota_state == OTA_STATE_DOWNLOADING), "ota_state error", err_1);

    CHECK_ERR(payload && len, "payload null or len = 0", err_1);

    CHECK_NZERO(ota_write(&offset, (char*)payload, len), "ota_write failed", err_1);
    ota_timestamp = aos_now_ms();
    ota_state = OTA_STATE_DOWNLOADING;
    return;
err_1:
    ota_state = OTA_STATE_ERROR;
    protocol_publish(proto, OTA_STATE_TOPIC, (uint8_t*)&ota_state, sizeof(ota_state), false, 0);
    return;
}

static void ota_end_callback(uint8_t msgid, uint8_t* payload, size_t len) {
    LOGD("Recv ota end info, len=%ld", len);
    if (ota_state == OTA_STATE_DOWNLOADING) {
        ota_state = OTA_STATE_DOWNLOAD_COMPLETE;
        ota_timestamp = aos_now_ms();
    }
    return;
}


/*******************************************************************************/
static void recv_irq_callback(void)
{
    aos_sem_signal(&recv_sem);
}

static void uart_proxy_task(void* arg)
{
    protocol_t proto = (protocol_t)arg;

    aos_sem_new(&recv_sem, 0);
    register_recv_irq_callback(uart_proxy_port, recv_irq_callback);

    is_recv_thread_run = true;
    while (is_recv_thread_run) {
        aos_sem_wait(&recv_sem, 200);
        uint8_t* pData;
        uint16_t size;
        if (pop_from_rx_ringbuffer(uart_proxy_port, &pData, &size) && size > 0) {
            protocol_prase(proto, pData, size);
        }
        if (!flush_tx_ringbuffer(uart_proxy_port)) {
            aos_sem_signal(&recv_sem);
        }
        aos_msleep(2);  // 延时2ms，避免串口一直有数据情况下阻塞其它任务
    }
}

bool uart_proxy_init(uint8_t port, uint32_t baudrate, const uart_proxy_cb_ops_t* cb)
{
    if (proto || !cb) {
        return false;
    }

    uart_proxy_port = port;

    uart_proxy_cb_ops = cb;
    Init_LPUART(uart_proxy_port, baudrate, 0);
    LPUART_recv_enable(uart_proxy_port, true);

    protocol_hal_t hal = hal_aos;
    hal.dev = (void*)&uart_proxy_port;

    proto = protocol_create(&hal);
    if (!proto) {
        return false;
    }

    protocol_subscribe(proto, MQTT_RECEIVE_TOPIC, mqtt_receive_callback);
    protocol_subscribe(proto, APP_VERSION_TOPIC, app_version_callback);

    protocol_subscribe(proto, OTA_INFO_TOPIC, ota_info_callback);
    protocol_subscribe(proto, OTA_DATA_TOPIC, ota_data_callback);
    protocol_subscribe(proto, OTA_END_TOPIC, ota_end_callback);

    protocol_subscribe(proto, OTA_STATE_TOPIC, app_state_callback);

    aos_task_new("uart_proxy_task", uart_proxy_task, proto, 8192);

    ota_hal_rollback(); // 在与 mqtt 升级共存时这里会重复调用,但也没啥影响

    return true;
}

bool uart_proxy_deinit(void)
{
    if (!proto) {
        return false;
    }
    is_recv_thread_run = false;
    recv_irq_callback();
    aos_msleep(100);
    Deinit_LPUART(uart_proxy_port);
    protocol_destroy(proto);
    proto = NULL;
    return true;
}

bool uart_proxy_mqtt_publish(const uint8_t* payload, size_t len)
{
    return protocol_publish(proto, MQTT_PUBLISH_TOPIC, payload, len, true, 100 + len); // 发送数据越长，等待时间越长
}
